/*-------------------------------------------------------------------------
 *
 * signalfuncs.c
 *	  用于信号后端的函数
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/storage/ipc/signalfuncs.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <signal.h>

#include "catalog/pg_authid.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
#include "storage/pmsignal.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/acl.h"
#include "utils/builtins.h"


/*
 * 向另一个后端发送信号。
 *
 * 如果用户是超级用户或与被信号的后端相同角色，则信号被传递。 对于“危险”的信号，在调用此函数之前需要进行超级用户的显式检查。
 *
 * 成功时返回0，普通故障返回1，常规权限错误返回2，如果调用者需要是超级用户则返回3。
 *
 * 如果发生一般故障（返回代码1），将发出警告消息。对于权限错误，这样做的责任在于调用者。
 */
#define SIGNAL_BACKEND_SUCCESS 0
#define SIGNAL_BACKEND_ERROR 1
#define SIGNAL_BACKEND_NOPERMISSION 2
#define SIGNAL_BACKEND_NOSUPERUSER 3
static int pg_signal_backend(int fc_pid, int fc_sig)
{
	PGPROC	   *fc_proc = BackendPidGetProc(fc_pid);

	/*
	 * BackendPidGetProc如果pid无效则返回NULL；但是到达kill()时，我们从这里获得有效proc的进程可能已经自行终止。没有办法对任意进程获取锁以防止这种情况。 但到目前为止，所有调用此机制的调用者无论如何都涉及某种结束进程的请求，因此它可能首先自行结束并不是问题。
	 *
	 * 注意，如果pid指的是辅助进程或主进程（都无法通过pg_signal_backend()发送信号），则proc也将为NULL。
	 */
	if (fc_proc == NULL)
	{
		/*
		 * 这只是一个警告，因此在循环遍历结果集时，如果某个后端在运行过程中自行终止，则不会中止。
		 */
		ereport(WARNING,
				(errmsg("PID %d is not a PostgreSQL backend process", fc_pid)));

		return SIGNAL_BACKEND_ERROR;
	}

	/*
	 * 只允许超级用户对超级用户拥有的后端发送信号。 任何不广告角色的进程可能具有超级用户拥有的后端的重要性，因此以这种方式对待它。
	 */
	if ((!OidIsValid(fc_proc->roleId) || superuser_arg(fc_proc->roleId)) &&
		!superuser())
		return SIGNAL_BACKEND_NOSUPERUSER;

	/* 用户可以对他们有角色成员资格的后端发送信号。 */
	if (!has_privs_of_role(GetUserId(), fc_proc->roleId) &&
		!has_privs_of_role(GetUserId(), ROLE_PG_SIGNAL_BACKEND))
		return SIGNAL_BACKEND_NOPERMISSION;

	/*
	 * 我们刚刚验证的进程能否在到达此处之前结束，然后pid被回收用于新进程？ 然后我们可能正在尝试杀死错误的东西。 在顺序pid分配和包装的情况下几乎不可能。 也许在pid重用随机化的系统上可能发生。 这种竞争条件的可能性似乎太不可能，不值得担忧。
	 */

	/* 如果我们有setsid()，信号整个后端的进程组 */
#ifdef HAVE_SETSID
	if (kill(-fc_pid, fc_sig))
#else
	if (kill(fc_pid, fc_sig))
#endif
	{
		/* 再次，仅为允许循环而发出警告 */
		ereport(WARNING,
				(errmsg("could not send signal to process %d: %m", fc_pid)));
		return SIGNAL_BACKEND_ERROR;
	}
	return SIGNAL_BACKEND_SUCCESS;
}

/*
 * 信号以取消一个后端进程。 如果您是正在取消的进程所属角色的成员，则允许进行此操作。
 *
 * 注意，只有超级用户可以对超级用户拥有的进程发送信号。
 */
Datum pg_cancel_backend(PG_FUNCTION_ARGS)
{
	int			fc_r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);

	if (fc_r == SIGNAL_BACKEND_NOSUPERUSER)
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be a superuser to cancel superuser query")));

	if (fc_r == SIGNAL_BACKEND_NOPERMISSION)
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend")));

	PG_RETURN_BOOL(fc_r == SIGNAL_BACKEND_SUCCESS);
}

/*
 * 等待直到没有具有给定PID的后端进程并返回true。
 * 超时时，发出警告并返回false。
 */
static bool pg_wait_until_termination(int fc_pid, int64 fc_timeout)
{
	/*
	 * 等待步骤为waittime毫秒，直到此函数退出或超时。
	 */
	int64		fc_waittime = 100;

	/*
	 * 最初剩余时间为用户指定的整个超时。
	 */
	int64		fc_remainingtime = fc_timeout;

	
/*
	 * 检查后端的存在。如果后端仍然存在，则等待
	 * waittime 毫秒，再次检查存在性。重复此过程
	 * 直到超时或发生错误或处理挂起的中断，例如查询
	 * 取消。
	 */
	do
	{
		if (fc_remainingtime < fc_waittime)
			fc_waittime = fc_remainingtime;

		if (kill(fc_pid, 0) == -1)
		{
			if (errno == ESRCH)
				return true;
			else
				ereport(ERROR,
						(errcode(ERRCODE_INTERNAL_ERROR),
						 errmsg("could not check the existence of the backend with PID %d: %m",
								fc_pid)));
		}

		/* 等待之前处理任何中断 */
		CHECK_FOR_INTERRUPTS();

		(void) WaitLatch(MyLatch,
						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
						 fc_waittime,
						 WAIT_EVENT_BACKEND_TERMINATION);

		ResetLatch(MyLatch);

		fc_remainingtime -= fc_waittime;
	} while (fc_remainingtime > 0);

	ereport(WARNING,
			(errmsg_plural("backend with PID %d did not terminate within %lld millisecond",
						   "backend with PID %d did not terminate within %lld milliseconds",
						   fc_timeout,
						   fc_pid, (long long int) fc_timeout)));

	return false;
}

/*
 * 发送信号以终止后台进程。如果您是被终止的进程所属角色的成员，则允许这样做。如果超时输入参数为 0，则此函数只是向后台发送信号并返回 true。如果超时非零，则等待直到没有进程拥有给定的 PID；如果进程在超时内结束，则返回 true，如果超时超过，则发出警告并返回 false。
 *
 * 请注意，只有超级用户可以发送信号给超级用户拥有的进程。
 */
Datum pg_terminate_backend(PG_FUNCTION_ARGS)
{
	int			fc_pid;
	int			fc_r;
	int			fc_timeout;		/* 毫秒 */

	fc_pid = PG_GETARG_INT32(0);
	fc_timeout = PG_GETARG_INT64(1);

	if (fc_timeout < 0)
		ereport(ERROR,
				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
				 errmsg("\"timeout\" must not be negative")));

	fc_r = pg_signal_backend(fc_pid, SIGTERM);

	if (fc_r == SIGNAL_BACKEND_NOSUPERUSER)
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be a superuser to terminate superuser process")));

	if (fc_r == SIGNAL_BACKEND_NOPERMISSION)
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend")));

	/* 只在成功时等待，并且如果确实请求了的话 */
	if (fc_r == SIGNAL_BACKEND_SUCCESS && fc_timeout > 0)
		PG_RETURN_BOOL(pg_wait_until_termination(fc_pid, fc_timeout));
	else
		PG_RETURN_BOOL(fc_r == SIGNAL_BACKEND_SUCCESS);
}

/*
 * 发送信号以重新加载数据库配置
 *
 * 该函数的权限检查通过正常的 GRANT 系统管理。
 */
Datum pg_reload_conf(PG_FUNCTION_ARGS)
{
	if (kill(PostmasterPid, SIGHUP))
	{
		ereport(WARNING,
				(errmsg("failed to send signal to postmaster: %m")));
		PG_RETURN_BOOL(false);
	}

	PG_RETURN_BOOL(true);
}


/*
 * 旋转日志文件
 *
 * 此函数保留以支持 adminpack 1.0。
 */
Datum pg_rotate_logfile(PG_FUNCTION_ARGS)
{
	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to rotate log files with adminpack 1.0"),
		/* 翻译者：%s 是 SQL 函数名称 */
				 errhint("Consider using %s, which is part of core, instead.",
						 "pg_logfile_rotate()")));

	if (!Logging_collector)
	{
		ereport(WARNING,
				(errmsg("rotation not possible because log collection not active")));
		PG_RETURN_BOOL(false);
	}

	SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
	PG_RETURN_BOOL(true);
}

/*
 * 旋转日志文件
 *
 * 该函数的权限检查通过正常的 GRANT 系统管理。
 */
Datum pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
{
	if (!Logging_collector)
	{
		ereport(WARNING,
				(errmsg("rotation not possible because log collection not active")));
		PG_RETURN_BOOL(false);
	}

	SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
	PG_RETURN_BOOL(true);
}
